package com.google.code.p.narcissus.tools.pdfcompare;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.util.PDFImageWriter;

import com.google.code.p.narcissus.core.NarcissusException;
import com.google.code.p.narcissus.core.PictureComparator;
import com.google.code.p.narcissus.core.model.ComparisonResult;
import com.google.code.p.narcissus.core.model.ReferenceMetaData;
import com.google.code.p.narcissus.core.screenshots.PictureComparisonMapper;

/**
 * Allows comparing 2 PDF files and extract all the differences
 * <p>
 * Apache's PDFBox library is used to perform the transformation of PDFs to images.
 * </p>
 * <p>
 * Create a new PDFComparator for each comparison you perform! Don't reuse one already used
 * </p>
 * 
 * @author Jérôme JADOULLE [JADOJ]
 */
public class PDFComparator {

	private static final String IMAGE_PNG = "png";

	private enum PDFType {
		Reference, Comparison
	};

	/**
	 * Compares 2 PDF files. The differences will be returned as a list of <code>PictureComparisonResult</code>s - one for each page compared.
	 * <p>
	 * PDFs are compared page by page. The 2 PDFs must have the same amount of pages to be compared.<br />
	 * Each page is extracted as an image, compared and a <code>PictureComparisonResult</code> is produced as a result.
	 * </p>
	 * @throws IOException 
	 */
	public List<ComparisonResult> comparePDFs(File referencePDF,  Map<Integer, ReferenceMetaData> exclusionZones, File comparedPDF) throws NarcissusException, IOException {
		PDDocument referencePDFDocument = null;
		PDDocument comparedPDFDocument = null;
		try {
			if (referencePDF == null || !referencePDF.exists() || !referencePDF.isFile()) {
				throw new InvalidParameterException("Please pass a valid PDF file as input (" + referencePDF.getAbsolutePath() + ")!");
			}
			
			referencePDFDocument = PDDocument.load(referencePDF);

			if (comparedPDF == null || !comparedPDF.exists() || !comparedPDF.isFile()) {
				throw new InvalidParameterException("Please pass a valid PDF file as input (" + comparedPDF.getAbsolutePath() + ")!");
			}
			comparedPDFDocument = PDDocument.load(comparedPDF);

			return comparePDFs(referencePDFDocument, referencePDF.getName().replace(".pdf", ""), comparedPDFDocument, comparedPDF.getName().replace(".pdf", ""), exclusionZones);
		} catch (IOException impossibleToLoadFileEx) {
		
			throw new NarcissusException("Input documents cannot be loaded as PDFs!  See stacktrace for more info", impossibleToLoadFileEx);
		}finally{
			if( referencePDFDocument != null )
	        {
				referencePDFDocument.close();
	        }
			if( comparedPDFDocument != null )
	        {
				comparedPDFDocument.close();
	        }
		}
	}

	private List<ComparisonResult> comparePDFs(PDDocument referencePDF, String referenceName, PDDocument comparisonPDF, String comparisonName) throws NarcissusException{
		return comparePDFs( referencePDF,  referenceName,  comparisonPDF,  comparisonName,null);
	}
	
	
	private List<ComparisonResult> comparePDFs(PDDocument referencePDF, String referenceName, PDDocument comparisonPDF, String comparisonName, Map<Integer,ReferenceMetaData> metaDatas) throws NarcissusException {
		int pageQuantity = referencePDF.getNumberOfPages();
		if (pageQuantity != comparisonPDF.getNumberOfPages()) {
			throw new NarcissusException("The 2 PDFs to compare have to be of the same length to be compared!");
		}

		// It is now certain that the 2 PDFs contain the same amount of pages.
		List<ComparisonResult> comparisonResults = new ArrayList<ComparisonResult>(pageQuantity);
		try {
			String outputDirectoryPath = System.getProperty("pdfComparator.directory.output.location");
			String testFolderPath = "";
			File pictureComparison = null;
			Date date = new Date();
			ComparisonResult comparisonResult = null;
			ReferenceMetaData referenceMetaDataForCurrentPage = null;
			
			extractPDFImages(referencePDF, comparisonPDF, referenceName, date, pageQuantity);
			
			for (int pageCounter = 1; pageCounter <= pageQuantity; pageCounter++) {
				if(metaDatas!=null && metaDatas.get(pageCounter)!=null){
					referenceMetaDataForCurrentPage = metaDatas.get(pageCounter);
					referenceMetaDataForCurrentPage.setCreationDate(new Date());
				}
				comparisonResult = compareImages(new File(generatePDFImagePath(referenceName, date, PDFType.Reference, pageCounter)), referenceMetaDataForCurrentPage, new File(generatePDFImagePath(referenceName, date, PDFType.Comparison, pageCounter)));
				testFolderPath = outputDirectoryPath + "/" + referenceName + "_testRun";
				pictureComparison = new File(testFolderPath + "/" + referenceName  + pageCounter + ".xml");
				PictureComparisonMapper.toXml(comparisonResult, pictureComparison);
				comparisonResults.add(comparisonResult);
				referenceMetaDataForCurrentPage = null;
			}
		} catch (IOException impossibleToComparePDFPagesEx) {
			// I doubt this one will happen often
			throw new NarcissusException("Internal exception while trying to compare 2 pdf pages!  See stacktrace for more info", impossibleToComparePDFPagesEx);
		}

		return comparisonResults;
	}

	private void extractPDFImages(PDDocument referenceDocument, PDDocument comparisonDocument, String referenceFileName, Date date, int pageQuantity) throws IOException {
		String outputDirectoryPath = System.getProperty("pdfComparator.directory.output.location");
		String testFolderPath = outputDirectoryPath + "/" + referenceFileName + "_testRun";
		File testFolder = new File(testFolderPath);
		if(!testFolder.exists()){
			testFolder.mkdirs();
		}
		
		String referenceFolderPath = outputDirectoryPath + "/references";
		File referenceFolder = new File(referenceFolderPath);
		if(!referenceFolder.exists()){
			referenceFolder.mkdirs();
		}
		
		String referencePrefix = generatePDFImagePrefix(referenceFileName, date, PDFType.Reference);
		String comparisonPrefix = generatePDFImagePrefix(referenceFileName, date, PDFType.Comparison);

		PDFImageWriter imageWriter = new PDFImageWriter();

		imageWriter.writeImage(referenceDocument, IMAGE_PNG, null, 1, pageQuantity, referencePrefix, BufferedImage.TYPE_INT_RGB, calculateResolution());
		imageWriter.writeImage(comparisonDocument, IMAGE_PNG, null, 1, pageQuantity, comparisonPrefix, BufferedImage.TYPE_INT_RGB, calculateResolution());
	}

	/**
	 * Format: ./comparisons/[yyyy-MM-dd HH'h'mm] - [REFERENCE_FILE_NAME]/["Reference" | "Comparison"]-page
	 */
	private String generatePDFImagePrefix(String referenceFileName, Date date, PDFType type) {
		SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH'h'mm");
		String filePath="";
		if(PDFType.Reference.equals(type)){
			filePath = System.getProperty("pdfComparator.directory.output.location") + "/references/" + referenceFileName;
		}else{
			
			filePath = System.getProperty("pdfComparator.directory.output.location") + "/" + referenceFileName+ "_testRun/" + referenceFileName;
		}
		return filePath;
	}

	/**
	 * Format: [yyyy-MM-dd HH'h'mm]-[REFERENCE_FILE_NAME]/["Reference" | "Comparison"]-page[PAGE_NUMBER].png
	 */
	private String generatePDFImagePath(String referenceFileName, Date date, PDFType type, int pageNumber) {
		return generatePDFImagePrefix(referenceFileName, date, type)  + pageNumber + ".png";
	}

	public ComparisonResult compareImages(File referenceImageFile, ReferenceMetaData referenceMetaData, File comparisonImageFile) throws NarcissusException, IOException {
		ComparisonResult comparison = new PictureComparator().compare(ImageIO.read(referenceImageFile), referenceMetaData, ImageIO.read(comparisonImageFile));
		// PictureComparisonMapper.toXml(pictureComparator, comparisonResult);
		return comparison;
	}

	private int calculateResolution() {
		return 96;
	}

}